package beantools;

import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import static java.util.Arrays.asList;

public class ClassFinder {


    private boolean scanJars;
    private Iterable<String> paths;
    private ClassFilter classVisitor;

    public ClassFinder(String include, String exclude, Class[] annotations, boolean scanJars, Iterable<String> paths) {
        classVisitor = new ClassFilter(include, exclude, annotations);
        this.scanJars = scanJars;
        this.paths = paths;
    }

    public ClassFinder(String include, String exclude, Class[] annotations, boolean scanJars) {
        this(include, exclude, annotations, scanJars, classPathPaths());
    }


    public static Iterable<String> classPathPaths(){
        return asList(System.getProperty("java.class.path").split(System.getProperty("path.separator")));
    }

    public Set<Class> find() throws IOException, ClassNotFoundException {
        Set<Class> res = new HashSet<Class>();

        for(String path : paths){
            if (path.endsWith(".jar")){
                if (scanJars){
                    System.out.print("\n\tScanning :" + path);
                    res.addAll(new JarScanner(path).scanJar(classVisitor));
                }
            }else{
                File dir = new File(path);
                System.out.print("\n\tScanning :" + dir.getCanonicalPath());
                res.addAll(new DirScanner(dir).scanDir(classVisitor));
            }
        }
        return res;
    }

    class JarScanner{
        private final String jarPath;

        public JarScanner(String jarPath) {
            this.jarPath = jarPath;
        }

        private  Set<Class> scanJar(ClassFilter classVisitor) throws IOException, ClassNotFoundException {
            Set<Class> res = new HashSet<Class>();
            File jarFile = new File(jarPath);
            Enumeration<JarEntry> entries = new JarFile(jarPath).entries();
            while (entries.hasMoreElements()) {
                JarEntry o =  entries.nextElement();

                Class<?> clazz = classVisitor.visitEntry(o.getName(), jarFile);
                if (clazz != null) res.add(clazz);
            }
            return res;
        }
    }

    static class DirScanner{

        private final File root;

        public DirScanner(File root) {
            this.root = root;
        }

        private Set<Class> scanDir(ClassFilter classVisitor) throws IOException, ClassNotFoundException {
            if (!root.isDirectory()) return new HashSet<Class>();
            return scanDirectory(root, classVisitor);
        }

        private  Set<Class> scanDirectory(File dir, ClassFilter classVisitor) throws IOException, ClassNotFoundException {
            Set<Class> res = new HashSet<Class>();
            for (File file : dir.listFiles()){
                if (file.isDirectory())
                    res.addAll(scanDirectory(file, classVisitor));
                else{
                    Class clazz = classVisitor.visitEntry(pathRelativeTo(root, file), file);
                    if (clazz != null) res.add(clazz);
                }
            }
            return res;
        }

        private String pathRelativeTo(File root, File file) throws IOException {
            String res = file.getCanonicalPath().replace(root.getCanonicalPath(), "");
            while (res.startsWith(System.getProperty("file.separator"))) res = res.substring(1);
            return res;
        }
    }

}
